package com.ht.api.callback.impl;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.sql.DataSource;

import com.ht.api.callback.CallbackFunction;
import com.ht.api.constant.CacheConstants;
import com.ht.api.constant.KfSessionStatus;
import com.ht.api.constant.Message;
import com.ht.api.db.Db;
import com.ht.api.db.DbConfig;
import com.ht.api.db.DbList;
import com.ht.api.db.Entity;
import com.ht.api.db.Mapper;
import com.ht.api.db.util.HtUtil;
import com.ht.api.db.util.RedisUtil;
import com.ht.api.open.OpenUtil;
import com.ht.api.service.ExternalContactService;
import com.ht.api.service.KfService;
import com.ht.api.service.OfficialService;
import com.ht.api.service.WwxService;
import com.ht.api.websocket.WebSocket;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 微信客服回调：客服消息事件
 * @author asq
 * @createTime 2025年1月17日 13:26:47
 */
@Slf4j
public class KfmsgCallbackFunction implements CallbackFunction{
	/** 拉取客服消息的分页游标的缓存key前缀 */
	private static final String KF_MSG_CURSOR = "kf_msg_cursor:";
	/** 接口：读取消息 */
	private static long API_ID = 1891364625451192320L;
    
    private WebSocket webSocket;
	
	@Override
	public void execute(String appid, Entity callback, JSONObject params) {
		// 获取应用对应的数据源
		DataSource ds = getTenantDs(appid);

		// 获取回调中的客服ID和token
		String openKfId = params.getStr("OpenKfId");
		String token = params.getStr("Token");
		
		// 获取本次客服ID要拉取消息的上次游标位置
		String cursor = RedisUtil.get(KF_MSG_CURSOR + openKfId);
		
		// 进行消息处理
		syncMsg(ds, appid, token, openKfId, cursor);
	}

	/**
	 * 拉取消息，根据消息类型的不同，触发下一步动作
	 * @param ds
	 * @param appid
	 * @param token
	 * @param openKfId
	 * @param cursor
	 */
	private void syncMsg(DataSource ds, String appid, String token, String openKfId, String cursor) {
		// 调用微信接口，拉取当前客服ID对应的消息列表
		JSONObject json = JSONUtil.createObj();
		json.set("open_kfid", openKfId);
		json.set("token", token);
		json.set("cursor", cursor);
		String msgs = OpenUtil.call(appid, API_ID, json);
		JSONObject msgJson = JSONUtil.parseObj(msgs);
		log.info("[KfmsgCallbackFunction]收到微信客户消息事件，拉取消息：{}", msgJson.toStringPretty());

		// 记录cursor
		cursor = msgJson.getStr("next_cursor");
		RedisUtil.set(KF_MSG_CURSOR + openKfId, cursor);

		// 处理收到的客服消息
		boolean hasMore = saveKfMsg(ds, appid, msgJson);
		if (hasMore) {
			log.info("[KfmsgCallbackFunction]hasMore=true，继续拉取更多消息...");
			syncMsg(ds, appid, token, openKfId, cursor);
		} else {
			log.info("[KfmsgCallbackFunction]hasMore=false，消息拉取完毕~");
		}
	}
	
	/**
	 * 处理收到的消息
	 * @param ds
	 * @param appid
	 * @param msgJson
	 * @return
	 */
	public boolean saveKfMsg(DataSource ds, String appid, JSONObject msgJson) {
		JSONArray msgList = msgJson.getJSONArray("msg_list");
		if (msgList != null && !msgList.isEmpty()) {
			for (int i = 0; i < msgList.size(); i++) {
				JSONObject msg = msgList.getJSONObject(i);
				String msgtype = msg.getStr("msgtype");
				String openKfid = msg.getStr("open_kfid");
				String externalUserid = msg.getStr("external_userid");
				if ("event".equals(msgtype)) {
					openKfid = msg.getByPath("event.open_kfid", String.class);
					externalUserid = msg.getByPath("event.external_userid", String.class);
				}
				
				// 查询客服会话
				Entity kfSession = Db.createSqlParser("kf_session")
						.where("open_kfid", openKfid)
						.where("openid", externalUserid)
						.findOne(ds);
				if (kfSession == null) {
					kfSession = createKfSession(ds, appid, openKfid, externalUserid, msg);
				}
				
				// 事件
				if ("event".equals(msgtype)) {
					JSONObject event = msg.getJSONObject("event");
					if ("enter_session".equals(event.getStr("event_type"))) {
						// 如果是已结束会话，则变为重新激活
						if (kfSession.getInt("status") == KfSessionStatus.OVER) {
							kfSession.set("status", KfSessionStatus.RESET);
							Db.insert(ds, kfSession);
						}
						
						// 推送消息
						Mapper.translate(ds, kfSession);
						getWebSocket().sendMessage(Message.createMsg(Message.SESSION, kfSession), getSessionUserid(ds, kfSession));
					}
				} else { // 消息
					String sendTime = HtUtil.formatTimeString(msg.getStr("send_time"));
					
					// 写入kfMessage表
					Entity kfMessage = Db.createEntity("kf_message");
					kfMessage.set("session_id", kfSession.getMainId());
					kfMessage.set("msgid", msg.getStr("msgid"));
					kfMessage.set("open_kfid", msg.getStr("open_kfid"));
					kfMessage.set("openid", msg.getStr("external_userid"));
					kfMessage.set("userid", kfSession.getStr("userid"));
					kfMessage.set("send_time", sendTime);
					kfMessage.set("origin", msg.getInt("origin"));
					kfMessage.set("msgtype", msg.getStr("msgtype"));

					JSONObject content = formatContent(msg);
					kfMessage.set("content", content);

					kfMessage.set("appid", appid);
					Db.insert(ds, kfMessage);
					
					// 更新会话表
					Entity kfSessionUpdate = Db.createEntity("kf_session");
					kfSessionUpdate.setMainId(kfSession.getMainId());
					kfSessionUpdate.set("msg", WwxService.formatMsg(msg));
					kfSessionUpdate.set("first_msg_time", sendTime);
					kfSessionUpdate.set("last_msg_time", sendTime);
					if (msg.getInt("origin") == 3) {
						// 客户消息相关数量
						Integer sessionMsgCnt = kfSession.getInt("session_msg_cnt");
						kfSessionUpdate.set("session_msg_cnt", sessionMsgCnt != null ? -sessionMsgCnt : 0);
						kfSessionUpdate.set("customer_msg_cnt", 1);
					}
					if (kfSession.getInt("status") == KfSessionStatus.OVER) {
						kfSessionUpdate.set("status", KfSessionStatus.RESET);
					}
					Db.insert(ds, kfSessionUpdate);
					
					// 更新会话与客服关联表kf_session_servicer_relation
					Entity kfSessionServicerRelationEntity = Db.createSqlParser("kf_session_servicer_relation")
							.where("session_id", kfSessionUpdate.getMainId())
							.order("start_time", "desc").limit(1).findOne(ds);
					if (kfSessionServicerRelationEntity != null) {
						Entity kfSessionServicerRelation = Entity.create("kf_session_servicer_relation");
						kfSessionServicerRelation.setMainId(kfSessionServicerRelationEntity.getMainId());
						kfSessionServicerRelation.set("first_msg_time", sendTime);
						kfSessionServicerRelation.set("last_msg_time", sendTime);
						kfSessionServicerRelation.set("customer_msg_cnt", 1);
						kfSessionServicerRelation.set("last_customer_msg_time", sendTime);
						Db.insert(ds, kfSessionServicerRelation);
					}
					
					// 推送消息
					getWebSocket().sendMessage(Message.createMsg(Message.MESSAGE, kfMessage), getSessionUserid(ds, kfSession));
					
					// 启动监控消息过期的任务
					KfService.monitorSessionTask(ds, kfSession);
				}
			}
		}
		
		// 返回是否还有更多消息
		return msgJson.getInt("has_more") == 1;
	}

	/**
	 * 创建一个客服会话
	 * @param ds
	 * @param appid
	 * @param openKfid
	 * @param externalUserid
	 * @param msg
	 * @return 
	 */
	private Entity createKfSession(DataSource ds, String appid, String openKfid, String externalUserid, JSONObject msg) {
		Entity kfSession = Db.createEntity("kf_session");
		kfSession.set("open_kfid", openKfid);
		kfSession.set("openid", externalUserid);
		JSONObject event = msg.getJSONObject("event");
		if (event != null) {
			String sceneParam = getSceneParam(event);
			kfSession.set("scene", event.getStr("scene"));
			kfSession.set("scene_param", sceneParam);
			kfSession.set("welcome_code", event.getStr("welcome_code"));
			
			// 首次进粉，更新广告位相关接粉数量
			ExternalContactService.updateInCntByPosition(ds, sceneParam, msg.getStr("send_time"));
		}
		kfSession.set("type", 0);
		kfSession.set("status", KfSessionStatus.WAIT);
		kfSession.set("send_time", HtUtil.formatTimeString(msg.getStr("send_time")));
		kfSession.set("appid", appid);
		Db.insert(ds, kfSession);
		
		log.info("[KfmsgCallbackFunction]首次事件或消息到达，创建会话完成~");
		return kfSession;
	}

	/**
	 * 获取场景参数
	 * @param event
	 * @return
	 */
	private String getSceneParam(JSONObject event) {
		try {
			String sceneParam = event.getStr("scene_param");
			if (StrUtil.isNotBlank(sceneParam)) {
				return URLEncoder.encode(sceneParam, "utf-8");
			} else {
				return null;
			}
		} catch (UnsupportedEncodingException e) {
			log.error("场景参数urlencode发生异常：{}", e.getMessage(), e);
			return "";
		}
	}
	
	/**
	 * 获取会话对应的接待人员或待接待人员列表
	 * @param ds
	 * @param kfSession
	 * @return
	 */
	private String[] getSessionUserid(DataSource ds, Entity kfSession) {
		// 如果会话已分配接待人员，则返回接待人员关联的登陆人员ID
		String userid = kfSession.getStr("userid");
		int status = kfSession.getInt("status");
		if (StrUtil.isNotBlank(userid) && status == KfSessionStatus.PROGRESS) {
			return new String[] {userid};
		}
		
		// 如果会话还没有分配任何接待人员，则返回该会话对应的客服帐号的所有接待人员ID
		String sceneParam = kfSession.getStr("scene_param");
		String sql = """
				select userid from fan_user_team_relation where team_id in(
				  select team_id from position_channel where state=? and sys_deleted=1
				) and sys_deleted=1 and status=0
				""";
		DbList list = Db.query(ds, sql, sceneParam);
		return list.stream().map(b -> b.getStr("userid")).toArray(String[]::new);
	}

	/**
	 * 返回应用的所属租户库数据源
	 * @param appid
	 * @return
	 */
	private DataSource getTenantDs(String appid) {
		Entity entity = RedisUtil.get(CacheConstants.KEY_APP + appid);
		return DbConfig.useTenantDb(entity.getLong("tenant_id"));
	}
    
    /**
     * 获取websocket对象
     * @return
     */
    private WebSocket getWebSocket() {
    	if (webSocket == null) {
    		webSocket = SpringUtil.getBean(WebSocket.class);
    	}
    	return webSocket;
    }


	public JSONObject formatContent (JSONObject params) {
		String msgtype = params.getStr("msgtype");

		JSONObject content = JSONUtil.createObj();
		content.set("msgtype", msgtype);
		switch (msgtype) {
			case "text":
				content.putByPath("text.content", params.getByPath("text.content"));
				break;

			case "image":
				String imageAttachUrl = WwxService.uploadFileToAliyun(params.getByPath(msgtype + ".media_id", String.class));
				content.putByPath("image.attach_url", imageAttachUrl);
				break;

			case "voice":

				String voiceAttachUrl = WwxService.uploadFileToAliyun(params.getByPath(msgtype + ".media_id", String.class));
				content.putByPath("voice.attach_url", voiceAttachUrl);
				break;

			case "video":
				String voideAttachUrl = WwxService.uploadFileToAliyun(params.getByPath(msgtype + ".media_id", String.class));
				content.putByPath("video.attach_url", voideAttachUrl);
				break;
		}

		return content;
	}


}
